前面講了很多如何控制元素的方法,今天要來講如何用事件讓使用者跟網頁互動。
DOM元素設定為監聽/接受事件,通常滿足某個條件之後會觸發事件,再執行程式碼回應事件,MDN上就有提供一個事件清單可以參考。
觸發事件大概有下面的方式:
事件物件先會被分配到事件目標,在分配之前會確定傳播路徑。這個傳播路徑決定事件如何被傳遞,等同攸關事件被觸發的順序,傳遞分成三個階段:捕獲、目標、冒泡,幾乎所有的事件都會冒泡。
引用W3C的圖片:
捕獲(Capture Phase):事件的傳遞會從目標的祖父開始,去尋找綁定捕獲事件的元素,會從window
傳到目標的父層。
目標(Target Phase):事件物件到達該事件的目標,但如果事件是不會冒泡的類型,就會在這個階段停止傳遞,比如focus
。
冒泡(Bubbling Phase):會以相反的順序傳播回去,尋找是否綁定冒泡事件的元素,綁定的事件會依照這個方向一一觸發,就像從水底冒泡泡到最上層,事件傳遞最終會到window
結束。
但不一定每個事件都有冒泡,那我們要怎麽知道我們想要使用的事件有沒有冒泡呢?可以看看規範,會告訴你每個事件的性質是什麽,也會告訴你有沒有冒泡。
關於事件的發生順序,我會借用javascript.info的程式碼,對畫面中的任一區塊的元素都新增一個事件,並且觀察事件發生的順序:
for(let element of document.querySelectorAll('*')) {
element.addEventListener("click", event => console.log(`Capturing: ${element.tagName}`), true);
element.addEventListener("click", event => console.log(`Bubbling: ${element.tagName}`));
}
下面是示範todolist的按鈕:
<body>
<main>
<section class="panel">
<div class="add_task">
<button class="add_task_button" type="button">add task</button>
</div>
</section>
</main>
</body>
畫面呈現:
點擊按鈕,產生一段捕獲及冒泡順序:
可以看到他從HTML開始捕獲,但最後結束的也是HTML。可能會想問那document
跟window
呢?這兩者不是元素,沒有出現在這上面,但實際上就是照前面提到的傳遞順序。
觸發事件的元素跟目標元素不一定一樣,例如在最外的容器設置事件,但是被點擊到的元素是event.target
(事件目標),每次被點擊的不一定一樣。
假設我們現在有這組範例:
<div class="task_information">
<div class="task_event">
<input class="finish_task" type="checkbox">
<input class="task_title" type="text" size="28" placeholder="type something here...">
</div>
<div class="task_mark">
<button class="task_important"><i class="fal fa-star"></i></button>
<button class="task_edit_button fa-solid fa-pen"></button>
</div>
</div>
畫面呈現是:
假如我們把事件綁在task_information
,那麽事件中的this
(=event.currentTarget
)的元素就是task_information
。讓我們從下面的程式碼中示範:
taskItemInformation.addEventListener('click',handler)
function handler(event){
console.log(event.currentTarget,this)//<div>,<div>
console.log(event.target)//<input>
}
那事件委派的好處,就是不用一一存取綁定元素,再一個個綁定事件,只要在共用的祖父元素加上事件,在處理事件中的程式碼設置觸發條件,就可以一併處理。
另外,在Day16的文章中曾經提到querySelectorAll()
因為是靜態的Nodelist,很難調整Javascript中後來新增的元素,這時也可以利用委派來解決。
回顧當時的示範程式碼,首先HTML的結構中有三個<li>
,當我新增一個<li>
並放進結構中,nodelist
維持一樣。
<h1>contestantNumber: 1</h1>
<ul>
<li>hotpotFlavor: Spicy Sichuan</li>
<li>hotpotIngredients:Beef slices, Tofu, Enoki mushrooms, Napa cabbage</li>
<li>beveragePairing: Jasmine tea
</li>
</ul>
這時我讓<ul>
綁事件,只要在<ul>
的範圍內點擊的文字就會變色。
//在最一開始取得li的list
const li = document.querySelectorAll('li');
//取外層容器作為等等新增元素的地方
const ul = document.querySelector('ul')
//新增第四個li
const newLi = document.createElement("li");
//對新的li新增文字
newLi.innerText = `extraDish: Crispy fried wontons`;
//在ul裡面新增剛剛的li
ul.appendChild(newLi);
console.log(li)//nodelist(3)
ul.addEventListener('click',(event)=>{
//點擊後,事件目標的顏色會變成藍色
event.target.style.color="blue";
})
點擊後的結果:
DOM 的事件傳遞機制:捕獲與冒泡
What is event bubbling and capturing?
事件處理
UI Events
Bubbling and capturing
Event delegation